主题
三方都讲透的经典题(二哥仅 3 篇短文),对比小林《TCP 三次握手与四次挥手面试题》《半连接和全连接队列》、JavaGuide TCP 页与面渣逆袭《TCP》(侧边栏进入)。
多版对比
「为什么是三次」的口径,是最大分歧点:面渣把「确认双方收发能力」当主答案(能及格但不完整);小林给出 RFC 793 的第一原因——防止历史连接:旧的 SYN 先到,服务端回确认号后,客户端发现确认号对不上(期望 101 收到 91)直接回 RST,靠第三次握手才拦得住历史连接;JavaGuide 补充工程视角——同步双方 ISN + 确认双向可达,第 2 步后只有客户端确认了闭环,服务端必须等第三次 ACK。
深度分层明显:小林到内核参数级——SYN 重传由 tcp_syn_retries 控制(默认 5 次,1+2+4+8+16+32≈63 秒)、半连接队列不只由 tcp_max_syn_backlog 决定(实测 512 只剩 256)、全连接队列 = min(somaxconn, backlog)、tcp_abort_on_overflow 的 0/1 取舍;JavaGuide 给排查命令与纠偏——tcp_fin_timeout 管的是 FIN_WAIT_2 不是 TIME_WAIT、tcp_tw_reuse 新内核默认 2 且仅 loopback 生效、CLOSE_WAIT 堆积是应用忘了 close;面渣最浅但有独门记忆点——保活计时器 2 小时 + 每 75 秒探测 10 次、第 3 次握手可以带数据而第一次不能(防攻击)。
TIME_WAIT:小林钉死机制(仅主动关闭方有、Linux 固定 60 秒、收到 FIN 会重置 2MSL);JavaGuide 给量化(短连接上限约 2.8 万端口 ÷ 60 秒)与「TIME_WAIT 多是协议正常,别急着治」的工程判断。
最优题解
三次握手的原因分两层说:表面是同步双方的初始序列号、确认双向通信能力;更本质的是防止历史连接建立——网络里迟到的旧 SYN 先到服务端,服务端回复后,客户端靠确认号发现这是历史连接,回 RST 终止,第三次握手就是这道闸门。所以两次不行:服务端无法确认「自己发的包对方收到了」,也无法拦住旧连接。
四次挥手是因为半关闭:收到 FIN 的一方可能还有数据没发完,ACK 和自己的 FIN 要分开,所以是四次;被动方如果没数据且开启延迟确认,ACK 和 FIN 可以合并,变成三次。TIME_WAIT 只出现在主动关闭方,停留 2MSL(Linux 固定 60 秒),作用是保证最后的 ACK 能重传、让旧连接的报文在网络里自然消亡。TIME_WAIT 过多先看是不是 HTTP 短连接——上长连接或连接池通常是正解;而大量 CLOSE_WAIT 才是 bug,说明应用没调 close。
追问链
追问:SYN 攻击怎么办? 参考答:SYN 洪泛打的是半连接队列——调大 backlog、减少 SYN+ACK 重试次数,核心是开启 tcp_syncookies:不占队列,用编码好的 cookie 直接回 SYN-ACK,合法客户端回 ACK 即建立。
追问:半连接队列满会怎样? 参考答:新 SYN 会被丢弃(不是回 RST);全连接队列满则看 tcp_abort_on_overflow——0 丢弃 ACK 让客户端重发(利于突发流量),1 直接回 RST 快速失败。观察 netstat 的 overflowed 计数判断是否要调大。
我的理解
这题的分水岭在「为什么三次」:背「确认收发能力」是面渣水平,说出「防历史连接」才是小林/JG 水准。工程侧记住两对易混:TIME_WAIT(正常,主动关闭方)vs CLOSE_WAIT(应用 bug);tcp_fin_timeout 管的到底是什么。小林的内核参数数字别死背,面试说「以手头内核的文档为准,但调过 syncookies 和 backlog」就够。